# https://gitee.com/yueyinqiu5990/tj12413601/blob/master/assignment1/question2/calculators/gauss_legendre_method.py
import typing

import sympy
import sympy.abc
import sympy.functions.special.polynomials

from question2.integration_calculator import Integration1dCalculatorAdaptive
from question2.integration_problem import IntegrationProblem1d


class GaussLegendreMethod(Integration1dCalculatorAdaptive):
    def _generate_steps_endlessly(
            self,
            problem: IntegrationProblem1d) \
            -> typing.Generator[float, None, None]:
        factor, function = GaussLegendreMethod.__get_modified_function(problem)
        for i in Integration1dCalculatorAdaptive.endless_range(1):
            p_and_w = GaussLegendreMethod.__get_points_and_weights(i)
            items = (w * function(p) for p, w in p_and_w)
            yield sum(items) * factor

    @staticmethod
    def __get_modified_function(
            problem: IntegrationProblem1d) \
            -> tuple[float, typing.Callable[[float], float]]:
        a = problem.lower_limit()
        b = problem.upper_limit()
        f = problem.integrand()

        w = (b - a) / 2
        b = (a + b) / 2

        def modified_function(x: float):
            return f(w * x + b)

        return w, modified_function

    @staticmethod
    def __get_points_and_weights(n: int):
        # TODO: 可以直接打表
        # 此方法是可以提前计算好后直接查表的，但因为是作业，此处写一个比较粗糙的计算过程以显示其原理
        # 在 n > 5 时此方法已经几乎无法接受了
        legendre = sympy.functions.special.polynomials.legendre(n, sympy.abc.x)
        legendre_diff = sympy.diff(legendre, sympy.abc.x)
        for x in sympy.solve(legendre, sympy.abc.x):
            x = float(x)
            d = float(legendre_diff.subs(sympy.abc.x, x))
            yield x, 2 / (1 - x ** 2) / (d ** 2)
